package metamodel.coreservices;

import metamodel.*;

import java.io.StreamTokenizer;
import java.io.Reader;
import java.io.IOException;
import java.util.*;

/**
 * User: felix
 * Date: 23.06.2005
 */
public class Deserializer {
    int index;
    ClassNode meta;
    StreamTokenizer lexer;
    StringMapper mapper=new DefaultStringMapper();
    private List index2obj;

    public Deserializer(ClassNode meta, Reader reader)
    {
        this.meta = meta;
        lexer=new StreamTokenizer(reader);
        lexer.wordChars('$','$');
    }

    public List deserializeCollection() throws IOException {
        List list=new LinkedList();
        while (lexer.nextToken()!=StreamTokenizer.TT_EOF){
            check("Expected '('",lexer.ttype=='(');
            lexer.pushBack();
            list.add(deserialize());
        }
        return list;
    }

    public Object deserialize() throws IOException {
        return deserialize(20);
    }

    public Object deserialize(int initalSize) throws IOException {
        if (index2obj==null)  // bad bug!
            index2obj=new ArrayList(initalSize);
        return typeCase(meta,null);


    }

    void matchLParen() throws IOException {
        match('(');
    }

    void matchRParen() throws IOException {
        match(')');
    }

    void matchLBracket() throws IOException {
        match('[');
    }

    void matchRBracket() throws IOException {
        match(']');
    }


    private void match(char c) throws IOException {
        if (lexer.nextToken()!=c)
          throw new RuntimeException("'"+c+"' expected in"+lexer.lineno());
    }

    private Object typeCase(ClassNode classNode, DeepProperty dp) throws IOException {

     Object ret;

     if(lexer.nextToken()=='#'){
        check("refrence number expected",lexer.nextToken()==StreamTokenizer.TT_NUMBER);
         return index2obj.get((int) lexer.nval);
     } else if (lexer.ttype==StreamTokenizer.TT_WORD) {
         check ("null expected",lexer.sval.equals("null"));
         return null;
     }
     else {
        check("'(' expected but got '"+lexer.ttype+"'",lexer.ttype=='(');
     }

     if (classNode.isPoly()){
            check("Identifier expected",lexer.nextToken()==StreamTokenizer.TT_WORD);
           String className=lexer.sval;
            //todo: Hacker Alarm: Modellierung muss explizit werden
           ClassNode concreteClassNode = classNode.getPoly().getClazzForConcreteTypeName(className);
            match(',');

         try {
             Class jclass=getClass().getClassLoader().loadClass(className);
             ret=jclass.newInstance();
             index2obj.add(ret);
         } catch (ClassNotFoundException e) {
             throw new RuntimeException(e);
         } catch (IllegalAccessException e) {
             throw new RuntimeException(e);
         } catch (InstantiationException e) {
             throw new RuntimeException(e);
         }


         iterateRecord(concreteClassNode,dp, ret);
     }     else {
          ret= classNode.createInstance(null);
         index2obj.add(ret);
          iterateRecord(classNode, dp, ret);
     }
     matchRParen();
     return ret;
 }

    private void check(String message,boolean b) {
        if (!b)
        throw new RuntimeException("Check failed: "+message+" @line:"+lexer.lineno());
    }

    private void  iterateRecord(ClassNode classNode, DeepProperty dp, Object me) throws IOException {

        for (Iterator iterator = classNode.getProperties().iterator(); iterator.hasNext();) {
            Property p =  (Property) iterator.next();
            iterateProperty(p, me);
            if (iterator.hasNext()){
                match(',');
            }
        }

    }

    private void iterateProperty(Property p, Object parent) throws IOException {

        if (p instanceof FlatProperty){
            FlatProperty flatProp=(FlatProperty) p;
            readFlatProperty(p, parent, flatProp);
            return ; //leaf node
        }


        if (p instanceof ObjectReference){
            ObjectReference objRef = (ObjectReference) p;
            p.setValue(parent,typeCase(objRef.getElementType(),objRef));
            return;
        }

        if (p instanceof CollectionRef){
            CollectionRef collRef=(CollectionRef) p;
            ArrayList list = new ArrayList(10);//todo: devise smart initial size..
            index2obj.add(list);
            iterateObjectCollection(collRef, list);
            p.setValue(parent,list);
            return;
        }
        throw new RuntimeException("Unknown property type");
    }

    private void readFlatProperty(Property p, Object parent, FlatProperty flatProp) throws IOException {
        if (lexer.nextToken()==StreamTokenizer.TT_WORD){
            check("expected symbol null got:'"+lexer.sval+"'",lexer.sval.equals("null"));
            p.setValue(parent,null);

        }else{
            check("expected string literal",lexer.ttype=='"');
            try{
            p.setValue(parent,mapper.toObject(flatProp,lexer.sval));
            } catch (RuntimeException ex){
                System.out.println("Problems writing property "+p.getName());
                throw ex;
            }
        }

    }


    private void iterateObjectCollection(CollectionRef collRef, Collection parent ) throws IOException {
        matchLBracket();
        if (lexer.nextToken()==']')
                return;
        lexer.pushBack();
        do{
            parent.add(typeCase(collRef.getElementType(), collRef));
        }  while(lexer.nextToken()==',');
        check("']' expected" ,lexer.ttype==']');

    }

    public void setMapper(final StringMapper mapper) {
        this.mapper = mapper;
    }

}
